## Методы `__add__, __sub__, __mul__`

Рассмотрим далее тему магических методов для работы с остальными арифметическими операторами:

![](img/magic_methods.png)

Как всегда, объяснить работу этих методов проще всего на конкретном примере.

Предположим, что мы создаем класс для работы с банковским счетом

Его экземпляры будут хранить имя клиента и его текущий баланс. 


```python

class BankAccount:
   
    def __init__(self, name, balance):  
        self.name = name
        self.balance = balance
         

user1 = BankAccount('Иван', 100)
user2 = BankAccount('Петр', 200)
```


Если же нам понадобиться изменить баланс текущего клиента в объекте `user1`, то сейчас это можно сделать через локальное свойство `balance`:

```python
user1.balance = user1.balance + 300
```
Мы добавили `300 рублей`. 

Но было бы здорово если бы это изменение можно было бы прописать вот так:

> user1 = user1 + 300

Конечно, при запуске программы возникнет ошибка, так как оператор сложения не работает с экземплярами класса `BankAccount`.

Однако, это можно поправить, если добавить в наш класс магический метод `__add__`. 

Я запишу его в следующем виде:

```python
    def __add__(self, other):
       return self.balance + other
      
```

И теперь, при запуске программы, все работает так, как и задумывалось. 

Давайте разберем этот момент подробнее.

Вначале у нас есть объект класса `BankAccount` со значением  `100`. Затем, арифметическая операция `user1 = user1 + 100` фактически означает выполнение команды:

```python
user1 = user1.__add__(100)
```

В результате, активируется метод `__add__` и параметр `other` принимает целочисленное значение `100`. 

Проверка проходит и формируется новый объект класса `BankAccount` со значением секунд `100+300 = 400`. 

Этот объект возвращается методом `__add__` и переменная `user1` начинает ссылаться на этот новый экземпляр класса. 

На прежний уже не будет никаких внешних ссылок, поэтому он будет автоматически удален сборщиком мусора.


Но возможно и следущие вариации работы с балансом

``` python
user1 + 300
user1 + user2
400 + user1
```

Реализация

```python
    def __add__(self, other):
        if isinstance(other, (int, float)):
            return self.balance + other
        elif isinstance(other, BankAccount):
            return self.balance + other.balance
        
    def __radd__(self, other):
        return self + other
```


### Далее разберем жизненный пример. Домовая книга, загс, регистрация граждан

```python
class Human:
    def __init__(self, name, sex):
        self.name = name
        self.sex = sex.upper()
        self.status = None
```

Создадим двух объектов класс Human.

```
ivan = Human('Иван', 'm')
anna = Human('Анна', 'w')

ivan.marry(anna)
print(anna.status)
print(ivan.status)
```

А теперь наша задача поженить их между собой. Пишем метод
```python
    def marry(self, human):
        if isinstance(human, Human):
            self.status = human
            human.status = self
        else:
            raise TypeError('Объект не человек')

john.marry(ann)
print(ann.status)
```


А теперь они захотели иметь детей. 

Т.к. мы живем в России – однополые браки у нас воспрещены, и дети у однополых родителей не д.б. 

Давайте станем еще категоричнее, это у нас учебный пример. Дети должны рождаться только в браке и только в любви.

Поэтому пишем метод делать детей с учетом проверки половой разницы родителей.

```python
def make_children(self, partner):
    if self.status == partner and self.sex != partner.sex:
        return Human(random.choice(['Оля', 'Коля', 'Толя', 'Поля']), random.choice(['M', 'W']))
    else:
        raise Exception('Нельзя иметь детей')
```

Ну и имя наследника хотим увидеть

```python
def __repr__(self):
    return self.name


john = Human('Джон', 'm')
mike = Human('Миша', 'm')
ann = Human('Аня', 'w')
elise = Human('Лиза', 'w')

john.marry(ann)
baby = ann.make_children(john)
print(baby)
print(ann.__dict__)
ann.make_children(mike) # искл
ann.make_children(elise) # искл
```


А теперь давайте будем женить наших персонажей с помощью обычных арифметических операций
К примеру `john + ann`

Переходим к понятию перегрузка операторов

В питоне для каждого оператора (к примеру математического) есть своя магия, волшебство, экспектопатронус.

Для этого вместо marry напишем магию `__add__`

Теперь пишем `john + ann` и всё работает.
`john + ann`

Проверим их статусы

```python
print(john.status)
print(ann.status)
```
А размножаться они будут с помощью оператора умножения

```python
def __mul__(self, partner):

baby = ann * john
print(baby)
```

Мы видим, что у Ани и Джона родился Коля

```
Аня
Джон
Коля
```
Магическая магия – умножение `__mul__`

Перегружать мы можем всё-что угодно. Почти. 

К примеру. 
`
`Eq` = eqil, `lt` = less then, `gt` = greater than, `__ge__(rhs)` = greater than or equal to rhs
`

### Снова перегрузка операторов

```python
class Человек:

    def __init__(self, возраст):
        self.возраст = возраст

    def __eq__(self, другой_объект):
        return self.возраст == другой_объект.возраст

    def __lt__(self, другой_объект):
        return self.возраст < другой_объект.возраст

    def __gt__(self, другой_объект):
        return self.возраст > другой_объект.возраст


вася = Человек(25)
петя = Человек(33)
федя = Человек(25)

print(вася == федя)
print(вася > федя)
print(вася > петя)
```

А теперь мы может не только сравнивать людей между собой, но и сортировать их по возрасту и т.п.
